Skip to content

Allow rules without codes - #28049

Merged
ntBre merged 8 commits into
brent/update-preview-category-defaultsfrom
brent/codeless-rules
Aug 27, 2026
Merged

Allow rules without codes#28049
ntBre merged 8 commits into
brent/update-preview-category-defaultsfrom
brent/codeless-rules

Conversation

@ntBre

@ntBre ntBre commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR allows adding rules that only belong to categories and only have names rather than requiring
a linter group and code.

The first non-empty commit uses Rule::name instead of Rule::noqa_code throughout the linter tests, causing a huge number of file renames without changes.

The second commit updates FixTable from a mapping of SecondaryCode -> (name, fix_count) to
DiagnosticId -> (Option<SecondaryCode>, fix_count). This doesn't really change its usage but
involves minor diffs to a substantial number of lines. It also allows us to drop some of the special
hashbrown handling and the hashbrown dependency in ruff_linter, which is nice.

The third commit contains the bulk of the change, including the macro changes needed to support
map_codes entries like that for the pytest-fixture-autouse rule in the fourth commit:

() => rules::ruff::rules::PytestFixtureAutouse,

These changes are pretty mechanical overall, just handling cases where the linter or noqa code could
be missing, which fortunately seemed pretty straightforward in every case.

The fourth commit takes advantage of these changes to reinstate RUF076 as our first purely
pedantic rule. I'm happy to drop that if it's too controversial or opens too many questions about
the previously-removed rule and its code, but I thought it was nice to have a motivating example. We
moved this rule from preview to removed because it was too pedantic for the RUF category, even
in preview, so it's exactly the kind of rule we can support now with the categories.

Finally, the fifth commit updates the contributing docs to reflect that linters and codes are
optional. This may need to be adjusted if we back out the RUF076 change.

Test Plan

Existing tests, plus a few new ones for pytest-fixture-autouse

@ntBre ntBre added preview Related to preview mode features rule-selection Related to enabling or disabling rules labels Aug 25, 2026
@astral-sh-bot

astral-sh-bot Bot commented Aug 25, 2026

Copy link
Copy Markdown

Typing conformance results

No changes detected ✅

Current numbers
The percentage of diagnostics emitted that were expected errors held steady at 97.79%. The percentage of expected errors that received a diagnostic held steady at 94.33%. The number of fully passing files held steady at 112/136.

@astral-sh-bot

astral-sh-bot Bot commented Aug 25, 2026

Copy link
Copy Markdown

Memory usage report

Memory usage unchanged ✅

(Flake8UsePathlib, "124") => rules::flake8_use_pathlib::violations::PyPath,
(Flake8UsePathlib, "201") => rules::flake8_use_pathlib::rules::PathConstructorCurrentDirectory,
(Flake8UsePathlib, "202") => rules::flake8_use_pathlib::rules::OsPathGetsize,
(Flake8UsePathlib, "202") => rules::flake8_use_pathlib::rules::OsPathGetsize,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops. Unrelated but noticed while I was here.

@astral-sh-bot

astral-sh-bot Bot commented Aug 25, 2026

Copy link
Copy Markdown

ecosystem-analyzer results

No diagnostic changes detected ✅

Full report with detailed diff (timing results)

@codspeed-hq

codspeed-hq Bot commented Aug 25, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 186 untouched benchmarks
⏩ 24 skipped benchmarks1


Comparing brent/codeless-rules (65201b7) with brent/update-preview-category-defaults (8e03ca8)

Open in CodSpeed

Footnotes

  1. 24 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@astral-sh-bot

astral-sh-bot Bot commented Aug 25, 2026

Copy link
Copy Markdown

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

ℹ️ ecosystem check detected linter changes. (+740 -0 violations, +0 -0 fixes in 8 projects; 51 projects unchanged)

apache/airflow (+579 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select ALL

+ airflow-core/tests/conftest.py:166:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/system/conftest.py:34:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/always/test_providers_manager.py:350:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/always/test_providers_manager.py:93:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/always/test_secrets_environment_variables.py:62:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api/common/test_trigger_dag.py:42:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/auth/managers/simple/conftest.py:34:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/auth/test_tokens.py:329:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/common/db/test_dags.py:45:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/common/test_dagbag.py:52:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/common/test_exceptions.py:113:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_asset_state_store.py:57:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_assets.py:1335:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_assets.py:1340:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_assets.py:2174:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_assets.py:310:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_assets.py:814:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_auth.py:101:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_auth.py:165:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_auth.py:40:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_backfills.py:77:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_config.py:165:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_config.py:513:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_config.py:579:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_connections.py:102:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_parsing.py:41:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_run.py:142:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_run.py:4432:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_sources.py:71:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_tags.py:106:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_versions.py:32:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_warning.py:50:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py:214:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_event_logs.py:69:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_extra_links.py:91:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_hitl.py:278:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_import_error.py:104:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_import_error.py:116:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_log.py:52:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
... 540 additional changes omitted for project

apache/superset (+111 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select ALL

+ tests/integration_tests/celery_tests.py:71:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/integration_tests/charts/api_tests.py:91:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/integration_tests/charts/data/api_tests.py:111:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/integration_tests/charts/version_restore_tests.py:68:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/integration_tests/conftest.py:120:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/integration_tests/dao/base_dao_test.py:72:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/integration_tests/dao/conftest.py:46:33: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/integration_tests/dashboards/filter_state/api_tests.py:56:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/integration_tests/dashboards/update_tabs_test.py:41:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/integration_tests/dashboards/version_restore_tests.py:69:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
... 101 additional changes omitted for project

bokeh/bokeh (+3 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select ALL

+ tests/codebase/conftest.py:29:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/test_examples.py:112:34: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/unit/bokeh/io/test_export.py:104:33: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators

langchain-ai/langchain (+16 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ libs/core/tests/unit_tests/conftest.py:12:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ libs/core/tests/unit_tests/language_models/chat_models/test_rate_limiting.py:12:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ libs/core/tests/unit_tests/load/test_secret_injection.py:35:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ libs/core/tests/unit_tests/runnables/conftest.py:10:34: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ libs/core/tests/unit_tests/runnables/test_tracing_interops.py:303:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ libs/core/tests/unit_tests/utils/test_gateway.py:23:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ libs/langchain_v1/tests/unit_tests/agents/middleware/core/test_tracing.py:23:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ libs/langchain_v1/tests/unit_tests/conftest.py:20:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ libs/partners/openai/tests/integration_tests/chat_models/conftest.py:28:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ libs/partners/openai/tests/integration_tests/chat_models/conftest.py:95:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
... 6 additional changes omitted for project

pypa/build (+4 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ tests/conftest.py:103:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/conftest.py:109:34: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/conftest.py:88:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/conftest.py:96:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators

... Truncated remaining completed project reports due to GitHub comment length restrictions

Changes by rule (1 rules affected)

code total + violation - violation + fix - fix
pytest-fixture-autouse 740 740 0 0 0

Formatter (stable)

✅ ecosystem check detected no format changes.

Formatter (preview)

✅ ecosystem check detected no format changes.

@ntBre
ntBre force-pushed the brent/codeless-rules branch from b36a3c4 to 8e75e0c Compare August 25, 2026 22:12
@ntBre
ntBre marked this pull request as ready for review August 25, 2026 22:34
@ntBre
ntBre requested a review from MichaReiser August 25, 2026 22:36

@MichaReiser MichaReiser left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you.

I think I'd prefer if we used rule.identifier in fewer places.

  • Let's move existing tests. While a little more work, it's not that involved and it simplifies the tests, they can simply call name. This also makes the test future prove for when we remove or deprecate codes. It also expresses that names are the preferred default now (I can't wait for when I don't need to remember both)
  • Let's review the uses of identifier in Support lint rules without legacy codes. Are there places where we can just use name because it isn't user facing? Are some of the uses user facing and need preview gating...

Comment thread crates/ruff/src/printer.rs
Comment thread crates/ruff_linter/src/registry.rs Outdated
Comment on lines +387 to +392
pub fn identifier(&self) -> LintIdentifier {
match self.noqa_code() {
Some(code) => LintIdentifier::Code(code),
None => LintIdentifier::Name(self.name()),
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason that we continue preferring the code over its name? Why can't we always use the name as identifier? Do we need to preview gate this?

Overall, the name identifier seems a bit confusing to me. A rule's unique identifier now and going forward is always its name. This seems to be mainly a compatibility layer, but it can't be for anything user facing, because it would otherwise have to be preview gated?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex is telling me that this breaks the ruff server.

With preview enabled and output-prefer-rule-codes = true, the server requests Noqa-style suppression edits, so this branch drops the edit for pytest-fixture-autouse. I reproduced that its “Disable for this line” action disappears; setting the option to false restores it. Fall back to a name-based ruff: ignore edit for codeless diagnostics in the server, regardless of the output preference.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My Codex told me this as well, but I don't think this is actually a problem. This requires:

  • preview enabled
  • output-prefer-rule-codes = true
  • activating the preview, category-only rule pytest-fixture-autouse that is also quite pedantic
  • trying to quick-fix the rule in an editor

and the only "breakage" is that we don't offer the ruff: ignore quickfix when you've expressed a preference for codes. My codex also tried to fix this and it seemed more trouble than it was worth to allow changing the suppression style per diagnostic (it's currently selected for the whole batch of diagnostics).

I also wanted to fix it initially but was put off at least by Codex's initial approach, and it's quite a niche use case anyway. I can take a closer look if you want, though.

Comment thread crates/ruff/src/commands/format.rs Outdated
let mut rule_names: Vec<_> = incompatible_rules
.into_iter()
.map(|rule| format!("`{}`", rule.noqa_code()))
.map(|rule| format!("`{}`", rule.identifier()))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This use seems user visible. Do we need to preview gate it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already preview-gated by virtue of the fact that the name-only rules are going to be in preview. There's no other option for rules without codes than to show the name, so I don't think there's any additional level of preview-gating we can apply. That's why identifier prefers codes when they're available, to preserve compatibility.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, we could just unwrap here for now. This is only used to warn for two rules that definitely have noqa codes.

Alternatively, are warnings part of our stable interface? It doesn't seem totally crazy to me just to switch this always to use names.

This also applies to this warning in the server:

tracing::warn!("Rule {} does not have an explanation", rule.noqa_code());
output.push_str("An issue occurred: an explanation for this rule was not found.");

We're down to only 3 uses of identifier including these two warnings.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious what you'll think about this, but I went ahead and unwrapped noqa_code here, used name_and_code in the server warning, and inlined identifier into RUF105, which allowed me to delete identifier completely.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, are warnings part of our stable interface? It doesn't seem totally crazy to me just to switch this always to use names.

They're part of what I consider Ruff's user interface. Which is why I'd expect it to follow my configuration. But I admit, it's probably not worth bothering too much about it, but we should use codes or names based on the user's configuration if we can.

Comment thread crates/ruff/src/commands/check.rs Outdated
Comment thread crates/ruff/src/commands/format.rs Outdated
let mut rule_names: Vec<_> = incompatible_rules
.into_iter()
.map(|rule| format!("`{}`", rule.noqa_code()))
.map(|rule| format!("`{}`", rule.identifier()))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this user facing? I think we need to use names here in preview

Comment thread crates/ruff/src/printer.rs Outdated
.inner
.iter()
.sorted_by_key(|diagnostic| diagnostic.secondary_code())
.sorted_by_key(|diagnostic| diagnostic.secondary_code_or_id())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we group by secondary_code_or_id and not by id and only store the secondary code if needed for display?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good catch, I didn't realize we were calling secondary_code again down below for the output. We can use name here too. I always find this whole fold pretty complicated to follow.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, codex pointed out that using name here has a very small impact on the stable output because the second sort below this won't reorder ties with the same count. So "F401 previously appeared before F821 [when they had the same count]; now F821 appears first." I think this is probably fine?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd have to take a closer look, but if it's user facing, I'd expect it to follow my setting. Again, I agree, that's a detail, so probably not worth spending much time on, but it degrades polish

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe an example is helpful here. I definitely know what you mean about degrading polish, and I think I've made two such suggestions on this PR 🙁 But the difference here is something like this:

 105     F403    undefined-local-with-import-star
   2     E402    module-import-not-at-top-of-file
-  1     B007    unused-loop-control-variable
-  1     F821    undefined-name
+  1     F821    undefined-name
+  1     B007    unused-loop-control-variable
   1     TID251  banned-api
   1     TID253  banned-module-level-imports

Just two rows flipping order in the --statistics output when they have the same count and their code-sort order differs from their name-sort order.

Of course, I can just put this back to secondary_code_or_id and restore the old sort order since there turned out to be a better answer to your very first question than I thought.

for rule in rules {
let name = rule.name();
let code = rule.noqa_code();
let code = rule.identifier();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use name_and_code here (and above)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name_and_code formats exactly as name (code), but this formats them differently:

            let _ = writeln!(output, "- [`{name}`](rules/{name}.md) (`{code}`)");

This code is actually problematic for a different reason since it loops over linters, disregarding rules that don't belong to a linter group. I think for now I'll just use noqa_code().unwrap() in light of that.

We'll have to stabilize both the categories themselves and a rule without a code for this to be a real problem, and I'm planning to include a secondary, category-focused rule page in my migration guide PR anyway.

Comment thread crates/ruff_dev/src/generate_docs.rs Outdated
Comment thread crates/ruff_dev/src/generate_docs.rs Outdated
@ntBre
ntBre force-pushed the brent/codeless-rules branch 2 times, most recently from 8f81cca to 2941dfc Compare August 26, 2026 22:48
@ntBre
ntBre requested a review from MichaReiser August 26, 2026 23:20
@MichaReiser

Copy link
Copy Markdown
Member

Can you squash some of your commit? It's otherwise very hard to review this PR because I can't review all changes because of the million snapshot changes, but going commit by commit only gives me partial changes, that I then need to keep in mind

@ntBre
ntBre force-pushed the brent/codeless-rules branch from 9c5afd2 to d8e40ef Compare August 27, 2026 13:07
@ntBre

ntBre commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

I squashed this back into the original structure from the PR summary and updated the summary itself to reflect the test changes. There's now never a Rule::identifier shim added. We just start using Rule::name where we can.

ntBre added 6 commits August 27, 2026 09:17
Summary
--

This PR allows adding rules that only belong to categories and only have names rather than requiring
a linter group and code.

The first non-empty commit introduces a `Rule::identifier` shim and uses it to replace
`Rule::noqa_code` calls mechanically in all the test snapshots.

The second commit updates `FixTable` from a mapping of `SecondaryCode -> (name, fix_count)` to
`DiagnosticId -> (Option<SecondaryCode>, fix_count)`. This doesn't really change its usage but
involves minor diffs to a substantial number of lines. It also allows us to drop some of the special
`hashbrown` handling and the `hashbrown` dependency in `ruff_linter`, which is nice.

The third commit contains the bulk of the change, including the macro changes needed to support
`map_codes` entries like that for the `pytest-fixture-autouse` rule in the fourth commit:

```rust
() => rules::ruff::rules::PytestFixtureAutouse,
```

These changes are pretty mechanical overall, just handling cases where the linter or noqa code could
be missing, which fortunately seemed pretty straightforward in every case.

The fourth commit takes advantage of these changes to reinstate `RUF076` as our first purely
`pedantic` rule. I'm happy to drop that if it's too controversial or opens too many questions about
the previously-removed rule and its code, but I thought it was nice to have a motivating example. We
moved this rule from `preview` to `removed` because it was too pedantic for the `RUF` category, even
in preview, so it's exactly the kind of rule we can support now with the categories.

Finally, the fifth commit updates the contributing docs to reflect that linters and codes are
optional. This may need to be adjusted if we back out the `RUF076` change.

Test Plan
--

Existing tests, plus a few new ones for `pytest-fixture-autouse`
avoid path-length issues on windows
share initial count writing

group by name in write_statistics

use rule name in generate_docs

simplify issue search query when code is unavailable

expect noqa codes for linter-based rules

remove Rule::identifier
@ntBre
ntBre force-pushed the brent/codeless-rules branch from d8e40ef to 1596c5a Compare August 27, 2026 13:20
Comment thread crates/ruff/src/commands/format.rs Outdated
let mut rule_names: Vec<_> = incompatible_rules
.into_iter()
.map(|rule| format!("`{}`", rule.noqa_code()))
.map(|rule| format!("`{}`", rule.noqa_code().unwrap()))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be easy to miss when stabilizing rule names. Can we show name (code) instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, then I can drop the debug_assert too.

Comment thread crates/ruff/src/printer.rs Outdated
Comment thread crates/ruff_dev/src/generate_default_rules.rs
Comment thread crates/ruff_wasm/tests/api.rs
Comment on lines +424 to +425
|| Rule::from_name(_rule)
.is_ok_and(|rule| matches!(rule.category(), Category::Testing))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is matching by category not sufficient? Shouldn't it allow us to at least remove RUF9?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _rule binding here is a little misleading because this iterator includes both full rule names like RUF901 and also prefixes like RUF9. We want to filter both out. I was confused by this initially and asked Codex earlier.

I also tested this today to make sure, and everything looks fine when running cargo dev generate-all because the testing feature isn't active, but the generate_json_schema test fails.

@ntBre ntBre mentioned this pull request Aug 27, 2026
@ntBre
ntBre merged commit aad0e90 into main Aug 27, 2026
109 of 110 checks passed
@ntBre
ntBre deleted the brent/codeless-rules branch August 27, 2026 15:26
sharkdp pushed a commit that referenced this pull request Aug 31, 2026
Summary
--

This PR allows adding rules that only belong to categories and only have
names rather than requiring
a linter group and code.

The first non-empty commit uses `Rule::name` instead of
`Rule::noqa_code` throughout the linter tests, causing a huge number of
file renames without changes.

The second commit updates `FixTable` from a mapping of `SecondaryCode ->
(name, fix_count)` to
`DiagnosticId -> (Option<SecondaryCode>, fix_count)`. This doesn't
really change its usage but
involves minor diffs to a substantial number of lines. It also allows us
to drop some of the special
`hashbrown` handling and the `hashbrown` dependency in `ruff_linter`,
which is nice.

The third commit contains the bulk of the change, including the macro
changes needed to support
`map_codes` entries like that for the `pytest-fixture-autouse` rule in
the fourth commit:

```rust
() => rules::ruff::rules::PytestFixtureAutouse,
```

These changes are pretty mechanical overall, just handling cases where
the linter or noqa code could
be missing, which fortunately seemed pretty straightforward in every
case.

The fourth commit takes advantage of these changes to reinstate `RUF076`
as our first purely
`pedantic` rule. I'm happy to drop that if it's too controversial or
opens too many questions about
the previously-removed rule and its code, but I thought it was nice to
have a motivating example. We
moved this rule from `preview` to `removed` because it was too pedantic
for the `RUF` category, even
in preview, so it's exactly the kind of rule we can support now with the
categories.

Finally, the fifth commit updates the contributing docs to reflect that
linters and codes are
optional. This may need to be adjusted if we back out the `RUF076`
change.

Test Plan
--

Existing tests, plus a few new ones for `pytest-fixture-autouse`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview Related to preview mode features rule-selection Related to enabling or disabling rules

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants